package strategies;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map.Entry;

import edu.uci.ics.jung.graph.UndirectedGraph;

import agents.Agent;
import agents.AverageUtility;
import base.Environment;
import plots.Edge;
import plots.Plot;

import static base.Environment.Configurations.*;

/**
 * Strategy generator used to update plot ratios and generate new strategies.
 */
public class LocalOptimumStrategyGenerator extends StrategyGenerator {
	
	protected double newThreshold;
	protected double delThreshold;
	protected double rndThreshold;
	protected int plotMultiplier;
	protected int chunkFactor;
	
	
	public LocalOptimumStrategyGenerator(Environment env) {
		super(env);
		this.newThreshold   = env.getDouble(NEW_THRESHOLD);
		this.delThreshold   = env.getDouble(DEL_THRESHOLD);
		this.plotMultiplier = env.getInt(PLOT_UPD_MULTIPLIER);
		this.chunkFactor    = env.getInt(CHUNK_FACTOR);
	}
	
	
	@Override
	public Strategy genStrategy(Agent agent) {
		int[] targets;
		int quota = agent.getQuota();
		int chunk = (int) (Math.random() * env.getInt(MAX_YIELD) / chunkFactor);
		int targetsSize = 0;
		
		while (targetsSize < StrategyGenerator.MIN_RATIO_NO)
			targetsSize = (int)(Math.random()*env.getSize());
		targets = new int[targetsSize];		
		
		while (quota > chunk) {
			targets[(int) (Math.random() * targets.length)] += chunk;
			quota -= chunk;
			chunk = (int) (Math.random() * env.getInt(MAX_YIELD) / chunkFactor);
		}
		
		if (quota > 0)
			targets[(int) (Math.random() * targets.length)] += quota;
		
		return genStrategy(targets);	
	}

	@Override
	public Strategy genStrategy(int[] targets) {		
		return new Strategy(genPath(targets));
	}
	
	/**
	 * Generates a new path with a target value of 0 for all nodes.
	 * Tries to make all the nodes of the path different.
	 * 
	 * @param length the length of the path
	 * 
	 * @return a new path
	 */
	@Override
	protected LinkedList<PlotTarget> genPath(int length) {
		if (length < 1) {
			throw new IllegalArgumentException("Path not long enough");
		}
		
		Plot[] castAux = new Plot[0];
		LinkedList<PlotTarget> path = new LinkedList<PlotTarget>();
		UndirectedGraph<Plot, Edge> graph = env.getPlots();
		ArrayList<Plot> usedPlots = new ArrayList<Plot>();
		
		Plot[] plots = graph.getVertices().toArray(castAux);
		Plot plot = plots[(int)(Math.random() * plots.length)];
		path.add(new PlotTarget(plot));
		usedPlots.add(plot);
		
		while (path.size() < length) {
			ArrayList<Plot> validPlots = new ArrayList<Plot>();
			
			plots = graph.getNeighbors(plot).toArray(castAux);
			for(Plot x : plots)
				if (!usedPlots.contains(x))
					validPlots.add(x);
			
			if (validPlots.isEmpty())
				plots = graph.getNeighbors(plot).toArray(castAux);
			else
				plots = validPlots.toArray(castAux);			
			plot = plots[(int)(Math.random() * plots.length)];
			
			if (!usedPlots.contains(plot))
				usedPlots.add(plot);
			path.add(new PlotTarget(plot));
		}
		
		return path;
	}
	
	/**
	 * Generates a new path with the specified target values for the respective nodes.
	 * 
	 * @param targets the harvest target values
	 * 
	 * @return a new path
	 */
	@Override
	protected LinkedList<PlotTarget> genPath(int[] targets) {
		LinkedList<PlotTarget> path = genPath(targets.length);
		for (int i = 0; i < targets.length; i++) {
			path.get(i).target = targets[i];
		}
		return path;
	}
	
	/**
	 * Generates a new path with the specified target values for the respective nodes.
	 * 
	 * @param plotTargets the plot and target pairs to be included in the path
	 * 
	 * @return a new path
	 */
	protected LinkedList<PlotTarget> genPath(List<PlotTarget> plotTargets) {
		if (plotTargets.size() < 1) {
			throw new IllegalArgumentException("Path not long enough");
		}
		
		HashMap<Plot, PlotTarget> plotsToCross = new HashMap<Plot, PlotTarget>();
		for (PlotTarget plotTarget : plotTargets) {
			plotsToCross.put(plotTarget.plot, plotTarget);
		}
		
		LinkedList<PlotTarget> path = new LinkedList<PlotTarget>();
		PlotTarget previous = plotTargets.get(0);
		plotsToCross.remove(previous.plot);
		path.add(previous);
		
		while (!plotsToCross.isEmpty()) {
			System.out.println(plotsToCross.size() + ": " + plotsToCross);
			
			PlotTarget next = plotsToCross.values().iterator().next();
			List<Plot> intermediate = env.getPath(previous.plot, next.plot);
			for (Plot plot : intermediate) {
				if (plotsToCross.containsKey(plot)) {
					path.add(plotsToCross.remove(plot));
				} else {
					path.add(new PlotTarget(plot));
				}
			}
			path.add(plotsToCross.remove(next.plot));
			previous = next;
		}
		
		return path;
	}
	
	@Override
	public void updateStrategiesTargets(Agent agent) {
		for (Strategy strategy : agent.getStrategies().keySet()) {
			updateStrategyTargets(strategy);
		}
	}
	
	@Override
	public List<Strategy> updateStrategiesPaths(Agent agent, int number) {
		LinkedList<Entry<Strategy, AverageUtility>> sorted =
				agent.getSortedStrategies(AverageUtility.DECREASING_UTILITY_COMPARATOR);
		ArrayList<Strategy> newStrategies = new ArrayList<Strategy>(sorted.size());
		for (int i = 0; i < number; i++) {
			double r = Math.random();
			Strategy strategy = sorted.get((int) (r * r * sorted.size())).getKey();
			newStrategies.add(updateStrategyPaths(strategy));
		}
		return newStrategies;
	}
	
	@Override
	protected void updateStrategyTargets(Strategy strategy) {
		LinkedList<PlotTarget> plotTargets = new LinkedList<PlotTarget>(strategy.path);
		Collections.sort(plotTargets);
		
		for (int size = plotTargets.size() * plotMultiplier; size > 0; size--) {
			double r = Math.random();
			int i = (int) (r * r * plotTargets.size());
			PlotTarget toDecrease = plotTargets.get(i);
			
			if (toDecrease.target > 0) {
				toDecrease.target--;
				r = Math.random();
				i = (int) ((1d - r * r) * plotTargets.size());
				if (i == plotTargets.size()) {
					i = plotTargets.size() - 1;
				}
				PlotTarget toIncrease = plotTargets.get(i);
				toIncrease.target++;
			}
		}
	
		// TODO: decide whether to clear the history here or not
		// note that we would also have to clear the associated history in the agent
		//strategy.clearUtilities();
	}

	@Override
	protected Strategy updateStrategyPaths(Strategy originalStrategy) {
		List<PlotTarget> sortedStrategyPlotTargets = originalStrategy.getSortedPlotTargets();
		
		// delete 0-target plots
		ListIterator<PlotTarget> iterator = sortedStrategyPlotTargets.listIterator();
		while (iterator.hasNext()) {
			PlotTarget plotTarget = iterator.next();
			if (plotTarget.target == 0) {
				iterator.remove();
			}
		}
		
		PlotTarget toReplace = sortedStrategyPlotTargets.get(sortedStrategyPlotTargets.size() - 1);
		List<Plot> sortedGlobalPlots = env.getSortedPlots();
		
		LinkedList<PlotTarget> newPlotTargets =
				new LinkedList<PlotTarget>(sortedStrategyPlotTargets);
		for (PlotTarget plotTarget : newPlotTargets) {
			sortedGlobalPlots.remove(plotTarget);
		}
		newPlotTargets.remove(toReplace);
		
		if (Math.random() < delThreshold || newPlotTargets.isEmpty()) {
			// if not delete the plot altogether, then add at least one other in its place
			if (!sortedGlobalPlots.isEmpty()) {
				do {
					double r = Math.random();
					int i = (int) (sortedGlobalPlots.size() * r * r);
					Plot newPlot = sortedGlobalPlots.remove(i);
					newPlotTargets.add(new PlotTarget(newPlot, toReplace.target));
				} while (!sortedGlobalPlots.isEmpty() && Math.random() >= newThreshold);
			}
		}
		
		return new Strategy(genPath(newPlotTargets));
	}
	
	
	/**
	 * Functionality tester for default graph for building strategy out of given plots and targets
	 * @param env
	 */
	public static void testFunctionality(Environment env){
		int[] targets = {10, 15, 3, 20};
		int[] plotIdxs = {0, 5, 3, 4};
		Plot[] plots = env.getPlots().getVertices().toArray(new Plot[0]);
		Plot[] plotsAsTargets = {plots[plotIdxs[0]], plots[plotIdxs[1]], plots[plotIdxs[2]], plots[plotIdxs[3]]};
		LocalOptimumStrategyGenerator gen = new LocalOptimumStrategyGenerator(env);
		
		System.out.println("Testing functionality of " + LocalOptimumStrategyGenerator.class.getCanonicalName() + "\n");
		
		Strategy s= new Strategy(gen.genPath(plotsAsTargets, targets));
		System.out.println("--- Strategy of plots " + Arrays.toString(plotIdxs) +
				" with targets "+ Arrays.toString(targets) +":");
		unveilPath(s);

		// Pause the execution
		try {
			System.in.read();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
